<?php

/**
 * Intelligent logger class that logs in the right place both in tasks and
 * normal pages.
 *
 * USAGE
 * =====
 *
 * vtnsLog::setSection('install');
 * vtnsLog::info('Starting installation...');
 * vtnsLog::debug('Config parameter: %s', $param);
 * vtnsLog::warning('Setting dangerous config parameter.');
 * vtnsLog::error('Invalid configuration parameter.');
 * vtnsLog::critical('Installation configuration failed!');
 *
 * vtnsLog::errorToMongo('Save error in mongo.');
 */
class vtnsLog
{
    private static $task = null;
    private static $section = '';

    const DEFAULT_LEVEL = 'info';

    const LOGGER_MONGO   = 'Mongo';
    const LOGGER_TASK    = 'Task';
    const LOGGER_SYMFONY = 'Symfony';

    const MONGO_PREFIX = 'mongo';

    const TASK_LEVEL    = 1;
    const LOGGER_METHOD = 2;
    const MONGO_LEVEL   = 3;
    const SEND_EMAIL    = 4;

    private static $levels = array(
        'debug' => array(
            self::TASK_LEVEL    => 'COMMENT',
            self::LOGGER_METHOD => 'debug',
            self::MONGO_LEVEL   => 'debug',
            self::SEND_EMAIL    => false),
        'info' => array(
            self::TASK_LEVEL    => 'INFO',
            self::LOGGER_METHOD => 'info',
            self::MONGO_LEVEL   => 'info',
            self::SEND_EMAIL    => false),
        'notice' => array(
            self::TASK_LEVEL    => 'NOTICE',
            self::LOGGER_METHOD => 'notice',
            self::MONGO_LEVEL   => 'notice',
            self::SEND_EMAIL    => false),
        'warning' => array(
            self::TASK_LEVEL    => 'INFO',
            self::LOGGER_METHOD => 'warning',
            self::MONGO_LEVEL   => 'warning',
            self::SEND_EMAIL    => false),
        'error' => array(
            self::TASK_LEVEL    => 'ERROR',
            self::LOGGER_METHOD => 'err',
            self::MONGO_LEVEL   => 'error',
            self::SEND_EMAIL    => false),
        'critical' => array(
            self::TASK_LEVEL    => 'ERROR',
            self::LOGGER_METHOD => 'err',
            self::MONGO_LEVEL   => 'critical',
            self::SEND_EMAIL    => true));

    public static function _setTask(fssTask $task)
    {
        self::$task = $task;
    }

    public static function setSection($section)
    {
        self::$section = $section;
    }

    public static function __callStatic($call, $arguments)
    {
        $logFunction = explode('To', $call, 2);

        if (count($logFunction) < 2) {
            $logger = self::$task ? self::LOGGER_TASK : self::LOGGER_SYMFONY;
        } elseif ($logFunction[1] === self::LOGGER_MONGO) {
            $logger = self::LOGGER_MONGO;
        } else {
            $logger = self::LOGGER_SYMFONY;
        }

        $withBackTrace = explode('WithBT', $logFunction[0], 2);
        $level = $withBackTrace[0];

        if (!array_key_exists($level, self::$levels)) {
            throw new Exception('Unable to log – unknown logging level '.$level);
        }

        if ($logger === self::LOGGER_MONGO) {
            $namespace = array_shift($arguments);
        }

        if (count($arguments) < 1
            || count($arguments) > 1 && strpos($arguments[0], '%') === -1)
        {
            // Either we have no arguments left for sprintf, or we have
            // invalid arguments
            throw new Exception(
                'Invalid arguments passed – if using the Mongo logging, you probably forgot the namespace.');
        }

        // Convert non scalar arguments to strings
        foreach ($arguments as &$argument) {
            $argument = self::convertToString($argument);
        }

        // Add calling function information to log
        if (count($withBackTrace) > 1) {
            $trace = debug_backtrace();
            // 2 is the depth of function calls above this
            $arguments[0] = '{'.$trace[2]['class'].'::'.$trace[2]['function'].'} - '.$arguments[0];
        }

        if ($logger !== self::LOGGER_MONGO && $logger !== self::LOGGER_SYMFONY) {
            // Add time information unless we're logging to mongo
            $arguments[0] = '['.date('c').'] '.$arguments[0];
        }

        $arguments = call_user_func_array('sprintf', $arguments);

        $logArgs = array($arguments, $level);


        if ($logger === self::LOGGER_MONGO) {
            array_unshift($logArgs, $namespace);
        }

        return call_user_func_array('self::logTo'.$logger, $logArgs);

        // FIXME: Use self::$levels[$level][self::SEND_EMAIL]
    }

    private static function convertToString($data)
    {
        if (is_int($data) || is_string($data) || method_exists($data, '__toString')) {
            $ret = (string) $data;
        } elseif (method_exists($data, 'getMessage')) {
            $ret = $data->getMessage();
        } else {
            $ret = self::normalize($data);
        }
        
        if (sfConfig::get('sf_environment') === 'prod')
            return is_scalar($ret)
                ? (is_string($ret)
                    ? str_replace(array('\\', "\n"), array('\\\\', '\\n'), $ret)
                    : $ret)
                : json_encode($ret);
        else
            return var_export($ret, true);
    }

    private static function normalize($data)
    {
        if (null === $data || is_scalar($data))
            return $data;

        if (is_array($data) || $data instanceof Traversable) {
            $normalized = array();
            foreach ($data as $key => $value)
                $normalized[$key] = self::normalize($value);

            return $normalized;
        }

        if (is_resource($data)) {
            return '[resource]';
        }

        return sprintf("[object] (%s: %s)", get_class($data), json_encode($data));
    }

    private static function logToTask($message, $level)
    {
        return self::$task->logSection(
            self::$section,
            $message,
            // Allow lines up to 36000 chars long
            36000,
            self::$levels[$level][self::TASK_LEVEL]);
    }

    private static function logToSymfony($message, $level)
    {
        return call_user_func(
            array(
                sfContext::getInstance()->getLogger(),
                self::$levels[$level][self::LOGGER_METHOD]),
            (self::$section ? '{'.self::$section.'} ' : '').$message);
    }

    private static function logToMongo($namespace, $message, $level)
    {
        return MongoLog::log(
            $namespace,
            self::$section ?: null,
            $message,
            $level);
    }
}

?>
